package com.mai4j.maiadmin.event;

import com.mai4j.maiadmin.event.pojo.SqlLog;
import com.mai4j.maiadmin.generated.Tables;
import com.mai4j.maiadmin.model.dto.SqlHistoryDTO;
import com.mai4j.maiadmin.service.SqlHistoryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jooq.*;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.TableImpl;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * 微信公众号请关注: 开源螺丝钉
 * <br>
 * 码云请关注: <a href="https://gitee.com/xiyoufang">https://gitee.com/xiyoufang</a>
 * <br>
 * 哔哩哔哩请关注: <a href="https://space.bilibili.com/438927834">https://space.bilibili.com/438927834</a>
 *
 * @author xiyoufang
 */
@Slf4j
@Component
public class SqlLogListener {
    /**
     * SQL解析器
     */
    private final Parser parser;

    private static final ThreadLocal<Set<String>> tablesTl = new ThreadLocal<>();
    /**
     * SQL记录
     */
    private final SqlHistoryService sqlHistoryService;

    public SqlLogListener(DSLContext context, SqlHistoryService sqlHistoryService) {
        this.sqlHistoryService = sqlHistoryService;
        DefaultConfiguration configuration = new DefaultConfiguration();
        configuration.setSQLDialect(context.dialect());
        configuration.setVisitListener(new SqlLogVisitListener() {
            @Override
            public void visitStart(VisitContext context) {
                if (context.queryPart() instanceof Named
                        && context.queryPart() instanceof TableImpl) {
                    tablesTl.get().add(((Named) context.queryPart()).getName());
                }
            }
        });
        parser = DSL.using(configuration).parser();
    }

    @Async
    @EventListener
    public void sqlLog(SqlLog sqlLog) {
        tablesTl.set(new HashSet<>());
        if (StringUtils.isBlank(sqlLog.getSqlText())) return;

        try (Query query = parser.parseQuery(sqlLog.getSqlText())) {
            query.getSQL(); // 用于触发SQL解析
            String statement = query.getClass().getSimpleName();
            Set<String> tables = tablesTl.get();
            if (tables.contains(Tables.SQL_HISTORY.getName()) && "InsertImpl".equalsIgnoreCase(statement)) {
                return; // 必须排除 INSERT SQL_HISTORY的记录，否则进入死循环
            }
            sqlHistoryService.save(new SqlHistoryDTO()
                    .setAppName(sqlLog.getAppName())
                    .setVisitor(sqlLog.getVisitor())
                    .setIp(sqlLog.getIp())
                    .setForwardedIp(sqlLog.getForwardedIp())
                    .setUserAgent(StringUtils.abbreviate(sqlLog.getUserAgent(), 512))
                    .setConnection(sqlLog.getConnection())
                    .setElapsed(sqlLog.getElapsed())
                    .setStatement(statement)
                    .setTableName(String.join(",", tables))
                    .setSqlText(sqlLog.getSqlText())
                    .setSqlDate(sqlLog.getSqlDate()));
        } catch (Exception e) { // SQL解析失败
            log.error(e.getMessage(), e);
        } finally {
            tablesTl.remove(); // 移除存储的表名
        }
    }

    public static abstract class SqlLogVisitListener implements VisitListener {

        @Override
        public void clauseStart(VisitContext context) {

        }

        @Override
        public void clauseEnd(VisitContext context) {

        }

        @Override
        public void visitEnd(VisitContext context) {

        }
    }
}
